//cMainWindow.cpp
#include "cMainWindow.h"

/* Default constructor.
 */
MainWindow::MainWindow()
{
	this->_Create("");
}

/* Constructor with name of file to open.
 */
MainWindow::MainWindow(std::string Filename)
{
	this->_Create(Filename);
}

/* PROTECTED: Internal constructor routine.
 */
void MainWindow::_Create(std::string Filename)
{
	char Title[32];
	GtkWidget *MainBox, *EventBox;
	
	sprintf(Title, "New Moon v%u", BUILD_ID);
	Window::_Create(Title, 800, 433);
	
	this->ShowBlockGrid = true;
	this->ScrollX = 0;
	this->ScrollY = 0;
	this->AnimTimer = 0;
	this->AnimDelay = 150;
	
	//Connect signals
	gtk_signal_connect(GTK_OBJECT(this->GTKWin), "delete-event",
		(GtkSignalFunc)cMainWindow_DeleteEvent, this);
	gtk_signal_connect(GTK_OBJECT(this->GTKWin), "key-press-event",
		(GtkSignalFunc)cMainWindow_KeyPress, this);
	gtk_signal_connect(GTK_OBJECT(this->GTKWin), "key-release-event",
		(GtkSignalFunc)cMainWindow_KeyRelease, this);
	
	
	//Main window box
    MainBox = gtk_vbox_new(FALSE, FALSE);
    gtk_container_add(GTK_CONTAINER(this->GTKWin), MainBox);
    
    
    //Event box for the drawing area
    EventBox = gtk_event_box_new();
    gtk_box_pack_start(GTK_BOX(MainBox), EventBox, TRUE, TRUE, 0);

    
    //The drawing area itself
    this->LevelBuf = new ImgBuf(256, 433);
    this->LevelBox = new DrawBox(256, 433); //This sets the minimum size
    
    this->LevelBox->InstallButtonCallback(cMainWindow_LevelBoxMouseButton,
    	this, false);
    
    this->LevelBox->InstallMouseMoveCallback(cMainWindow_LevelBoxMouseMove,
    	this, false);
    	
    this->LevelBox->InstallConfigureCallback(cMainWindow_LevelBoxConfigureEvent,
    	this, true);
    	
    this->LevelBox->InstallExposeCallback(cMainWindow_LevelBoxExposeEvent,
    	this, false);

	gtk_container_add(GTK_CONTAINER(EventBox),
		this->LevelBox->GetDrawingArea());
	
	
	//Status label
	this->StatusLabel = GTK_LABEL(gtk_label_new("Ready."));
	gtk_misc_set_alignment(GTK_MISC(this->StatusLabel), 0, 0.5);
	gtk_box_pack_start(GTK_BOX(MainBox), GTK_WIDGET(this->StatusLabel),
		FALSE, FALSE, 0);
	
	
	//If we have a filename, open it.
	if(Filename != "")
	{
		//TODO: pop up error message, or render it in the LevelBox
		if(!this->ROM.Open(Filename)) DebugOut(DO_MAJOR, "Cannot open ROM.\n");
		else
		{
			this->CurrentLevel.UseFile(&this->ROM);
			this->LoadLevel(0x105);
			//this->LoadLevel(0x1E0);
			//this->ScrollX = 16;
		}
	}
	
	gtk_widget_show_all(GTK_WIDGET(this->GTKWin));
	this->PalWin = new PaletteWindow(this);
	this->TileWin = new TileWindow(this);
	this->Map16Win = new Map16Window(this);
}

/* Destructor.
 */
MainWindow::~MainWindow()
{
	if(this->GTKWin) gtk_widget_destroy(GTK_WIDGET(this->GTKWin));
	this->GTKWin = NULL;
	if(this->PalWin) delete this->PalWin; this->PalWin = NULL;
	if(this->TileWin) delete this->TileWin; this->TileWin = NULL;
	if(this->Map16Win) delete this->Map16Win; this->Map16Win = NULL;
}

/* Loads the specified level number.
 * Inputs:
 * -Num: Level number.
 * Returns: true on success, false on failure.
 * Notes:
 * -Use this instead of calling CurrentLevel methods directly to ensure the
 *  window updates correctly (e.g. the title is changed to reflect the new
 *  level number).
 */
bool MainWindow::LoadLevel(u16 Num)
{
	char Title[9001]; //a bit more than the maximum name length (8192) to ensure
	//it doesn't overflow if some nut actually puts a name that long in... may
	//seem silly but someone could potentially distribute a malicious
	//levelnames.txt that exploits such an overflow
	
	if(this->ROM.LevelName[Num] == "")
		sprintf(Title, "Level %03X - %s - New Moon v%u", Num,
			this->ROM.InternalName.c_str(), BUILD_ID);
	else sprintf(Title, "%s [%03X] - %s - New Moon v%u",
		this->ROM.LevelName[Num].c_str(), Num, this->ROM.InternalName.c_str(),
		BUILD_ID);
	this->SetTitle(Title);
	if(!this->CurrentLevel.Load(Num)) return false;
	if(this->PalWin && this->PalWin->PaletteBox) this->PalWin->Redraw();
	if(this->TileWin && this->TileWin->TileBox) this->TileWin->Redraw();
	if(this->Map16Win && this->Map16Win->TileBox) this->Map16Win->Redraw();
		
	if(!this->AnimTimer) this->AnimTimer = g_timeout_add(this->AnimDelay,
		cMainWindow_AnimTimer, this);
	return true;
}

/* FRIEND: Callback for animation timer.
 * Inputs:
 * -Win: The MainWindow.
 * Returns: TRUE to keep the timer running, FALSE to destroy it.
 * Notes:
 * -Be sure to clear Win->AnimTimer if returning false, else it will try to
 *  destroy it twice.
 */
gboolean cMainWindow_AnimTimer(gpointer Win)
{
	MainWindow *_this = (MainWindow*)Win;
	
	if(!_this->EnableAnimation) return TRUE;
	_this->AnimFrame = (_this->AnimFrame + 1) & 15;
	_this->CurrentLevel.DrawAnimationFrame(_this->AnimFrame);
	_this->LevelBox->InvalidateAll();
	if(_this->PalWin) _this->PalWin->Redraw();
	if(_this->TileWin) _this->TileWin->Redraw();
	if(_this->Map16Win) _this->Map16Win->Redraw();
	return TRUE;
}

/* FRIEND: Callback for window keypress event.
 * Inputs:
 * -Widget: The GTK window.
 * -Event: The event.
 * -Win: The MainWindow.
 * Returns: TRUE to stop other handlers, FALSE to propagate further.
 */
gboolean cMainWindow_KeyPress(GtkWidget *Widget, GdkEventKey *Event,
gpointer Win)
{
	MainWindow *_this = (MainWindow*)Win;
	switch(Event->keyval)
	{
		case GDK_Left:
			if(_this->ScrollX)
			{
				_this->ScrollX--;
				_this->LevelBox->InvalidateAll(); //Force redraw
			}
			else gtk_widget_keynav_failed(Widget, GTK_DIR_LEFT);
		return TRUE;
		
		case GDK_Right:
			//todo: 31 * 16 is the maximum scroll for a horizontal level (it
			//would leave only the last screen visible). Calculate this so the
			//last screen ends up at the edge of the window, and don't scroll
			//vertical levels this way.
			if(_this->ScrollX < (31 * 16))
			{
				_this->ScrollX++;
				_this->LevelBox->InvalidateAll(); //Force redraw
			}
			else gtk_widget_keynav_failed(Widget, GTK_DIR_RIGHT);
		return TRUE;
		
		case GDK_Page_Up:
			_this->LoadLevel((_this->CurrentLevel.GetNum() - 1) & 0x1FF);
			_this->ScrollX = 0;
			_this->ScrollY = 0;
			_this->LevelBox->InvalidateAll(); //Force redraw
		return TRUE;
		
		case GDK_Page_Down:
			_this->LoadLevel((_this->CurrentLevel.GetNum() + 1) & 0x1FF);
			_this->ScrollX = 0;
			_this->ScrollY = 0;
			_this->LevelBox->InvalidateAll(); //Force redraw
		return TRUE;
		
		//THESE KEYS WILL PROBABLY BE CHANGED IN LATER VERSIONS
		case GDK_A:
			_this->AnimFrame = (_this->AnimFrame + 1) & 0xF;
			_this->CurrentLevel.DrawAnimationFrame(_this->AnimFrame);
			_this->LevelBox->InvalidateAll();
			if(_this->PalWin) _this->PalWin->Redraw();
			if(_this->TileWin) _this->TileWin->Redraw();
			if(_this->Map16Win) _this->Map16Win->Redraw();
		break;
		
		case GDK_a:
			_this->EnableAnimation = !_this->EnableAnimation;
		break;
	}
	return FALSE;
}

/* FRIEND: Callback for window key release event.
 * Inputs:
 * -Widget: The GTK window.
 * -Event: The event.
 * -Win: The MainWindow.
 * Returns: TRUE to stop other handlers, FALSE to propagate further.
 */
gboolean cMainWindow_KeyRelease(GtkWidget *Widget, GdkEventKey *Event,
gpointer Win)
{
	//MainWindow *_this = (MainWindow*)Win;
	return FALSE;
}

/* FRIEND: Callback for level DrawBox mouse button event.
 * Inputs:
 * -Box: The DrawBox.
 * -DrawingArea: The box's drawing area.
 * -Event: The event.
 * -Press: true if button was pressed, false if it was released.
 * -Win: Pointer to the MainWindow the box belongs to.
 */
void cMainWindow_LevelBoxMouseButton(DrawBox* Box, GtkWidget* DrawingArea,
GdkEventButton* Event, bool Press, void *Win)
{
}

/* FRIEND: Callback for level DrawBox mouse move event.
 * Inputs:
 * -Box: The DrawBox.
 * -DrawingArea: The box's drawing area.
 * -Event: The event.
 * -X, Y: The mouse cursor position relative to the drawing area.
 * -State: The mouse button state.
 * -Win: Pointer to the MainWindow the box belongs to.
 */
void cMainWindow_LevelBoxMouseMove(DrawBox* Box, GtkWidget* DrawingArea,
GdkEventMotion* Event, u32 X, u32 Y, GdkModifierType State, void *Win)
{
	MainWindow *_this = (MainWindow*)Win;
	char Buf[128], Buf2[64];
	std::string ObjName = "";
	SMWObject *Obj;
	
	if(State & GDK_BUTTON3_MASK)
	{
		_this->ScrollX++;
		Box->InvalidateAll();
	}
	else if(State & GDK_BUTTON1_MASK)
	{
		if(_this->ScrollX) _this->ScrollX--;
		Box->InvalidateAll();
	}
	else
	{
		X = (X / 16) + _this->ScrollX;
		Y = (Y / 16) + _this->ScrollY;
		Obj = _this->CurrentLevel.GetObjectAt(X, Y);
		if(Obj)
		{
			if(Obj->Type == SMWObject::OBJ_NORMAL)
				sprintf(Buf2, "Obj 0x%X", Obj->ObjID);
			else sprintf(Buf2, "Extended Obj 0x%X", Obj->ObjID);
		}
		else sprintf(Buf2, "No object");
		
		sprintf(Buf, "%u,%u: Tile %04X, %s", X, Y,
			_this->CurrentLevel.GetTileAt(X, Y), Buf2);
			
		if(Obj)
		{
			if(Obj->GetHasSubType())
			{
				sprintf(Buf2, "-%X", Obj->SubType);
				strncat(Buf, Buf2, sizeof(Buf) - strlen(Buf));
			}
			
			ObjName = _this->ROM.ObjectName[
				Obj->GetID(_this->CurrentLevel.Header.Tileset)];
			if(ObjName == "") ObjName = "<unknown>";
			
			sprintf(Buf2, " (%ux%u @ %u,%u) ", Obj->Width, Obj->Height,
				Obj->XPos, Obj->YPos);
			//sprintf(Buf2, " (%X) ", Obj->GetID(_this->CurrentLevel.Header.Tileset));
			strncat(Buf, Buf2, sizeof(Buf) - strlen(Buf));
		}
		
		ObjName = Buf + ObjName;
		gtk_label_set_text(_this->StatusLabel, ObjName.c_str());
	}
}

/* FRIEND: Callback for level DrawBox configure (resize, create etc) event.
 * Inputs:
 * -Box: The DrawBox.
 * -DrawingArea: The box's drawing area.
 * -Event: The event.
 * -Win: Pointer to the MainWindow the box belongs to.
 */
void cMainWindow_LevelBoxConfigureEvent(DrawBox* Box, GtkWidget* DrawingArea,
GdkEventConfigure* Event, void *Win)
{
	MainWindow *_this = (MainWindow*)Win;
	
	Box->SetFGColour(0, 0, 0);
	Box->DrawRectangle(0, 0, 0, 0, true); //Clear
	if(_this->LevelBuf) _this->LevelBuf->Resize(Event->width, Event->height);
}

/* FRIEND: Callback for level DrawBox expose (need repaint) event.
 * Inputs:
 * -Box: The DrawBox.
 * -DrawingArea: The box's drawing area.
 * -Event: The event.
 * -Win: Pointer to the MainWindow the box belongs to.
 */
void cMainWindow_LevelBoxExposeEvent(DrawBox* Box, GtkWidget* DrawingArea,
GdkEventExpose* Event, void *Win)
{
	MainWindow *_this = (MainWindow*)Win;
	char Buf[128];
	u32 Screen = 1 + (_this->ScrollX / 16);
	u32 ScrollXPx = _this->ScrollX * 16;
	
	_this->LevelBuf->Clear(0, 0, 0, 0);
	_this->CurrentLevel.Render(_this->LevelBuf, _this->ScrollX, _this->ScrollY);
	Box->BlitFrom(_this->LevelBuf, 0, 0, 0, 0, _this->LevelBuf->GetWidth(),
		_this->LevelBuf->GetHeight());
	
	//Draw grid and screen numbers
	if(_this->ShowBlockGrid)
	{
		Box->SetFGColour(64, 64, 64);
		for(u32 y=16; y<Box->GetHeight(); y += 16)
		{
			if(y == 256) Box->SetFGColour(0, 255, 255); //Subscreen boundary
			Box->DrawLine(0, y, Box->GetWidth(), y);
			if(y == 256) Box->SetFGColour(64, 64, 64);
		}
		for(u32 x=16; x<Box->GetWidth(); x += 16)
		{
			if(!((x + ScrollXPx) & 0xFF)) Box->SetFGColour(0, 255, 255); //Screen boundary
			Box->DrawLine(x, 0, x, Box->GetHeight());
			if(!((x + ScrollXPx) & 0xFF))
			{
				//this won't draw anything for the first screen when it's at the
				//very left, but right now I'm not concerned about that because
				//the debug text covers it anyway.
				if(_this->CurrentLevel.ScreenExit[Screen])
				{
					sprintf(Buf, "%u -> %03X", Screen,
						_this->CurrentLevel.ScreenExit[Screen]);
				}
				else sprintf(Buf, "%u", Screen);
				Screen++;
				Box->DrawText(x + 1, 0, Buf);
				Box->SetFGColour(64, 64, 64);
			}
		}
	}
	
	//Debug info
	sprintf(Buf, "%u, %u", _this->ScrollX, _this->ScrollY);
	Box->SetFGColour(255, 255, 0);
	Box->DrawText(0, 0, Buf);
}

/* FRIEND: Callback for delete_event (window was closed).
 * Inputs:
 * -Widget: Widget that received the event (i.e. the GTK Window).
 * -Event: The event.
 * -Win: The MainWindow object.
 * Returns: TRUE to stop other handlers, FALSE to propagate further.
 */
gboolean cMainWindow_DeleteEvent(GtkWidget *Widget, GdkEvent *Event, gpointer Win)
{
	MainWindow *_this = (MainWindow*)Win;
	if(_this->LevelBox) delete(_this->LevelBox); _this->LevelBox = NULL;
	if(_this->LevelBuf) delete(_this->LevelBuf); _this->LevelBuf = NULL;
	if(_this->AnimTimer) g_source_remove(_this->AnimTimer); _this->AnimTimer =0;
	exit(0);
	return TRUE;
}
